// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.intel_syntax noprefix
#include <AsmOffsets.inc>         // generated by the build from AsmOffsets.cpp
#include <unixasmmacros.inc>

//
// See PUSH_COOP_PINVOKE_FRAME, this macro is very similar, but also saves RAX/RDX and accepts the register
// bitmask in RCX
//
// On entry:
//  - BITMASK: bitmask describing pushes, may be volatile register or constant value
//  - RAX: managed function return value, may be an object or byref
//  - preserved regs: need to stay preserved, may contain objects or byrefs
//
// INVARIANTS
// - The macro assumes it is called from a prolog, prior to a frame pointer being setup.
// - All preserved registers remain unchanged from their values in managed code.
//
.macro PUSH_PROBE_FRAME threadReg, trashReg, BITMASK
    push_register    rdx                         // save RDX, it might contain an objectref
    push_register    rax                         // save RAX, it might contain an objectref
    lea             \trashReg, [rsp + 0x18]
    push_register   \trashReg                    // save caller`s RSP
    push_nonvol_reg r15                          // save preserved registers
    push_nonvol_reg r14                          //   ..
    push_nonvol_reg r13                          //   ..
    push_nonvol_reg r12                          //   ..
    push_nonvol_reg rbx                          //   ..
    push_register   \BITMASK                     // save the register bitmask passed in by caller
    push_register   \threadReg                   // Thread * (unused by stackwalker)
    push_nonvol_reg rbp                          // save caller`s RBP
    mov             \trashReg, [rsp + 11*8]      // Find the return address
    push_register   \trashReg                    // save m_RIP
    lea             \trashReg, [rsp + 0]         // trashReg == address of frame

    // allocate space for xmm0, xmm1 and alignment
    alloc_stack     0x28

    // save xmm0 and xmm1 in case they are used as return values
    movdqa          [rsp + 0x10], xmm0
    movdqa          [rsp + 0]   , xmm1

    // link the frame into the Thread
    mov             [\threadReg + OFFSETOF__Thread__m_pDeferredTransitionFrame], \trashReg
.endm

//
// Remove the frame from a previous call to PUSH_PROBE_FRAME from the top of the stack and restore preserved
// registers and return value to their values from before the probe was called (while also updating any
// object refs or byrefs).
.macro POP_PROBE_FRAME
    movdqa      xmm1, [rsp + 0]
    movdqa      xmm0, [rsp + 0x10]
    add         rsp, 0x28 + 8  // skip xmm0, xmm1 and discard RIP
    pop         rbp
    pop         rax     // discard Thread*
    pop         rax     // discard BITMASK
    pop         rbx
    pop         r12
    pop         r13
    pop         r14
    pop         r15
    pop         rax     // discard caller RSP
    pop         rax
    pop         rdx
.endm

//
// The prolog for all GC suspension hijacks (normal and stress). Fixes up the hijacked return address, and
// clears the hijack state.
//
// Register state on entry:
//  All registers correct for return to the original return address.
//
// Register state on exit:
//  R11: thread pointer
//  RCX: return value flags
//  RAX, RDX preserved, other volatile regs trashed
//
.macro FixupHijackedCallstack
        // preserve RAX, RDX as they may contain return values
        push rax
        push rdx

        // rax = GetThread(), makes nested calls
        INLINE_GETTHREAD
        mov r11, rax

        pop rdx
        pop rax

        // Fix the stack by pushing the original return address
        mov         rcx, [r11 + OFFSETOF__Thread__m_pvHijackedReturnAddress]
        push        rcx

        // Fetch the return address flags
        mov         rcx, [r11 + OFFSETOF__Thread__m_uHijackedReturnValueFlags]

        // Clear hijack state
        xor         r9, r9
        mov         [r11 + OFFSETOF__Thread__m_ppvHijackedReturnAddressLocation], r9
        mov         [r11 + OFFSETOF__Thread__m_pvHijackedReturnAddress], r9
        mov         [r11 + OFFSETOF__Thread__m_uHijackedReturnValueFlags], r9
.endm

//
// GC Probe Hijack target
//
NESTED_ENTRY RhpGcProbeHijack, _TEXT, NoHandler
        END_PROLOGUE
        FixupHijackedCallstack

        test        dword ptr [C_VAR(RhpTrapThreads)], TrapThreadsFlags_TrapThreads
        jnz         LOCAL_LABEL(WaitForGC)
        ret

LOCAL_LABEL(WaitForGC):
        or          ecx, DEFAULT_FRAME_SAVE_FLAGS  + PTFF_SAVE_RAX + PTFF_SAVE_RDX
        jmp         C_FUNC(RhpWaitForGC)
NESTED_END RhpGcProbeHijack, _TEXT

NESTED_ENTRY RhpWaitForGC, _TEXT, NoHandler
        PUSH_PROBE_FRAME r11, rax, rcx
        END_PROLOGUE

        mov         rbx, r11
        mov         rdi, [rbx + OFFSETOF__Thread__m_pDeferredTransitionFrame]
        call        C_FUNC(RhpWaitForGC2)

        mov         rax, [rbx + OFFSETOF__Thread__m_pDeferredTransitionFrame]
        test        dword ptr [rax + OFFSETOF__PInvokeTransitionFrame__m_Flags], PTFF_THREAD_ABORT
        jnz         LOCAL_LABEL(Abort)
        POP_PROBE_FRAME
        ret
LOCAL_LABEL(Abort):
        POP_PROBE_FRAME
        mov         rcx, STATUS_REDHAWK_THREAD_ABORT
        pop         rdx         // return address as exception RIP
        jmp         C_FUNC(RhpThrowHwEx) // Throw the ThreadAbortException as a special kind of hardware exception

NESTED_END RhpWaitForGC, _TEXT


LEAF_ENTRY RhpGcPoll, _TEXT
        cmp         dword ptr [C_VAR(RhpTrapThreads)], TrapThreadsFlags_None
        jne         LOCAL_LABEL(RhpGcPoll_RarePath)
        ret
LOCAL_LABEL(RhpGcPoll_RarePath):
        jmp C_FUNC(RhpGcPollRare)
LEAF_END RhpGcPoll, _TEXT

NESTED_ENTRY RhpGcPollRare, _TEXT, NoHandler
        PUSH_COOP_PINVOKE_FRAME rdi
        END_PROLOGUE
        call        C_FUNC(RhpGcPoll2)
        POP_COOP_PINVOKE_FRAME
        ret
NESTED_END RhpGcPollRare, _TEXT


#ifdef FEATURE_GC_STRESS

//
// GC Stress Hijack targets
//
LEAF_ENTRY RhpGcStressHijack, _TEXT
    // NYI
    int 3
LEAF_END RhpGcStressHijack, _TEXT

#endif  // FEATURE_GC_STRESS
